/*
 * Copyright 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * The Flash applet which provides binary sockets functionality for JavaScript.
 * For communication with JavaScript this applet uses ExternalInterface class.
 *
 * @author Aleksey Lagoshin
 */

/**
 * The identifier of Flash applet in the current web page.
 * Is using for multiple applets in one page.
 */
var id = root.loaderInfo.parameters.id;

/**
 * The socket object.
 */
var socket: Socket = new Socket();
/**
 * The input buffer, all received data are stored here.
 */
var buffer: ByteArray;

// Names of JavaScript functions
var FUNC_READY = "flashbin_" + id + "_onReady";
var FUNC_CONNECTED = "flashbin_" + id + "_onConnect";
var FUNC_CLOSED = "flashbin_" + id + "_onClose";
var FUNC_DATA = "flashbin_" + id + "_onData";
var FUNC_IOERROR = "flashbin_" + id + "_onIOError";
var FUNC_SECURITY_ERROR = "flashbin_" + id + "_onSecurityError";

var skipLF: Boolean = false;

// Trying to load a security file from specified URL
var crossdomain = root.loaderInfo.parameters.crossdomain;
if (crossdomain != null)
  Security.loadPolicyFile(crossdomain);

// Declaration of Flash -> JavaScript communication functions

socket.addEventListener(Event.CLOSE, onClose);
socket.addEventListener(Event.CONNECT, onConnect);
socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);

// Declaration of JavaScript -> Flash communication functions

ExternalInterface.addCallback("loadPolicyFile", loadPolicyFile);
ExternalInterface.addCallback("connect", connect);
ExternalInterface.addCallback("close", close);
ExternalInterface.addCallback("flush", flush);
ExternalInterface.addCallback("available", bytesAvailable);
ExternalInterface.addCallback("connected", connected);
ExternalInterface.addCallback("getEndian", getEndian);
ExternalInterface.addCallback("setEndian", setEndian);
ExternalInterface.addCallback("skip", skip);
ExternalInterface.addCallback("readBoolean", readBoolean);
ExternalInterface.addCallback("readByte", readByte);
ExternalInterface.addCallback("readBytes", readBytes);
ExternalInterface.addCallback("readDouble", readDouble);
ExternalInterface.addCallback("readFloat", readFloat);
ExternalInterface.addCallback("readInt", readInt);
ExternalInterface.addCallback("readShort", readShort);
ExternalInterface.addCallback("readUnsignedByte", readUnsignedByte);
ExternalInterface.addCallback("readUnsignedInt", readUnsignedInt);
ExternalInterface.addCallback("readUnsignedShort", readUnsignedShort);
ExternalInterface.addCallback("readUTF", readUTF);
ExternalInterface.addCallback("readString", readMultiByte);
ExternalInterface.addCallback("readStringLine", readStringLine);
ExternalInterface.addCallback("writeBoolean", writeBoolean);
ExternalInterface.addCallback("writeByte", writeByte);
ExternalInterface.addCallback("writeBytes", writeBytes);
ExternalInterface.addCallback("writeDouble", writeDouble);
ExternalInterface.addCallback("writeFloat", writeFloat);
ExternalInterface.addCallback("writeInt", writeInt);
ExternalInterface.addCallback("writeShort", writeShort);
ExternalInterface.addCallback("writeUnsignedInt", writeUnsignedInt);
ExternalInterface.addCallback("writeUTF", writeUTF);
ExternalInterface.addCallback("writeString", writeMultiByte);

ExternalInterface.call(FUNC_READY, null);

// Implementation of Flash -> JavaScript communication functions

function onClose(event: Event): void {
  ExternalInterface.call(FUNC_CLOSED, null);
}

function onConnect(event: Event): void {
  ExternalInterface.call(FUNC_CONNECTED, null);
}

function onIOError(event: IOErrorEvent): void {
  ExternalInterface.call(FUNC_IOERROR, event.text);
}

function onSecurityError(event: SecurityErrorEvent): void {
  ExternalInterface.call(FUNC_SECURITY_ERROR, event.text);
}

function onSocketData(event: ProgressEvent): void {
  var wasPosition = buffer.position;
  buffer.position = buffer.length;
  var bytes = new ByteArray();
  socket.readBytes(bytes);
  buffer.writeBytes(bytes);
  buffer.position = wasPosition;
  ExternalInterface.call(FUNC_DATA, event.bytesLoaded);
}

// Implementation of JavaScript -> Flash communication functions

function loadPolicyFile(crossdomain: String): void {
  Security.loadPolicyFile(crossdomain);
}

function connect(ip: String, port: int): void {
  buffer = new ByteArray();
  try {
    socket.connect(ip, port);
  }
  catch (e: SecurityError) {
    ExternalInterface.call(FUNC_SECURITY_ERROR, e.message);
  }
  catch (e: IOError) {
    ExternalInterface.call(FUNC_IOERROR, e.message);
  }
}

function close(): String {
  try {
    socket.close();
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function flush(): String {
  try {
    socket.flush();
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function bytesAvailable(): Object {
  try {
    return buffer.bytesAvailable;
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function connected(): Boolean {
  return socket.connected;
}

function getEndian(): String {
  return socket.endian;
}

function skip(bytes: int): int {
  if (bytes <= 0)
    return 0;

  var skipped = buffer.bytesAvailable < bytes ? buffer.bytesAvailable : bytes;

  buffer.position += skipped;

  return skipped;
}

function setEndian(endian: String): void {
  socket.endian = endian;
}

function readBoolean(): Object {
  try {
    return buffer.readBoolean();
  }
  catch (e: EOFError) {
    return "E2" + e.message;
  }
  return null;
}

function readByte(): Object {
  try {
    return buffer.readByte();
  }
  catch (e: EOFError) {
    return "E2" + e.message;
  }
  return null;
}

function readBytes(length: uint): String {
  var result: String;

  if (buffer.bytesAvailable < length)
    length = buffer.bytesAvailable;

  try {
    result = "0";
    while (length-- > 0)
      result += String.fromCharCode(buffer.readByte());

    return result;
  }
  catch (e: EOFError) {
    return "E2" + e.message;
  }
  return null;
}

function readDouble(): Object {
  try {
    return buffer.readDouble();
  }
  catch (e: EOFError) {
    buffer.position += buffer.bytesAvailable;
    return "E2" + e.message;
  }
  return null;
}

function readFloat(): Object {
  try {
    return buffer.readFloat();
  }
  catch (e: EOFError) {
    buffer.position += buffer.bytesAvailable;
    return "E2" + e.message;
  }
  return null;
}

function readInt(): Object {
  try {
    return buffer.readInt();
  }
  catch (e: EOFError) {
    buffer.position += buffer.bytesAvailable;
    return "E2" + e.message;
  }
  return null;
}

function readShort(): Object {
  try {
    return buffer.readShort();
  }
  catch (e: EOFError) {
    buffer.position += buffer.bytesAvailable;
    return "E2" + e.message;
  }
  return null;
}

function readUnsignedByte(): Object {
  try {
    return buffer.readUnsignedByte();
  }
  catch (e: EOFError) {
    return "E2" + e.message;
  }
  return null;
}

function readUnsignedInt(): Object {
  try {
    return buffer.readUnsignedInt();
  }
  catch (e: EOFError) {
    buffer.position += buffer.bytesAvailable;
    return "E2" + e.message;
  }
  return null;
}

function readUnsignedShort(): Object {
  try {
    return buffer.readUnsignedShort();
  }
  catch (e: EOFError) {
    buffer.position += buffer.bytesAvailable;
    return "E2" + e.message;
  }
  return null;
}

function readUTF(): String {
  var position = buffer.position;
  try {
    return "0" + buffer.readUTF();
  }
  catch (e: EOFError) {
    buffer.position = position;
    return "0";
  }
  return null;
}

function readMultiByte(length: uint, charSet: String): String {
  try {
    return "0" + buffer.readMultiByte(length, charSet);
  }
  catch (e: EOFError) {
    return "E2" + e.message;
  }
  return null;
}

function readStringLine(ignoreLF: Boolean, charSet: String): String {
  var crLf: String = "\r\n";
  var cr: int = crLf.charCodeAt(0);
  var lf: int = crLf.charCodeAt(1);

  var eol: Boolean = false;
	var skipFirstLF = false;
  var omitLF = ignoreLF || skipLF;

  var i;
  for (i = buffer.position; i < buffer.length; i++) {
    if (buffer[i] == cr || buffer[i] == lf) {
      if (i == buffer.position && omitLF && buffer[i] == lf) {
        omitLF = false;
        skipFirstLF = true;
      }
      else {
        eol = true;
        break;
      }
    }
  }

  if (eol) {
    // Skipping LF if needed
    if (skipFirstLF)
      buffer.readByte();
    var result: String;
    result = "0" + buffer.readMultiByte(i - buffer.position, charSet);
    // Skipping the "\r" or "\n" character
    buffer.readByte();

    skipLF = (buffer[i] == cr);
    return result;
  }

  return null;
}

function writeBoolean(value: Boolean): String {
  try {
    socket.writeBoolean(value);
	}
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeByte(value: int): String {
  try {
    socket.writeByte(value);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeBytes(bytesStr: String): String {
  try {
    for (var i = 0; i < bytesStr.length; i++)
      socket.writeByte(bytesStr.charCodeAt(i));
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeDouble(value: Number): String {
  try {
    socket.writeDouble(value);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeFloat(value: Number): String {
  try {
    socket.writeFloat(value);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeInt(value: int): String {
  try {
    socket.writeInt(value);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeShort(value: int): String {
  try {
    socket.writeShort(value);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeUnsignedInt(value: uint): String {
  try {
    socket.writeUnsignedInt(value);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeUTF(value: String): String {
  try {
    socket.writeUTF(value);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}

function writeMultiByte(value: String, charSet: String): String {
  try {
    socket.writeMultiByte(value, charSet);
  }
  catch (e: IOError) {
    return "E1" + e.message;
  }
  return null;
}